// Copyright (C) Kumo inc. and its affiliates.
// Author: Jeff.li lijippy@163.com
// All rights reserved.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//

#include <nebula/types/meta/map_type.h>
#include <nebula/types/meta/struct_type.h>
#include <turbo/base/checked_cast.h>

namespace nebula {

    constexpr Type::type MapType::type_id;

    MapType::MapType(std::shared_ptr<DataType> key_type, std::shared_ptr<DataType> item_type,
                     bool keys_sorted)
            : MapType(::nebula::field("key", std::move(key_type), false),
                      ::nebula::field("value", std::move(item_type)), keys_sorted) {}

    MapType::MapType(std::shared_ptr<DataType> key_type, std::shared_ptr<Field> item_field,
                     bool keys_sorted)
            : MapType(::nebula::field("key", std::move(key_type), false), std::move(item_field),
                      keys_sorted) {}

    MapType::MapType(std::shared_ptr<Field> key_field, std::shared_ptr<Field> item_field,
                     bool keys_sorted)
            : MapType(
            ::nebula::field("entries",
                            STRUCT({std::move(key_field), std::move(item_field)}), false),
            keys_sorted) {}

    MapType::MapType(std::shared_ptr<Field> value_field, bool keys_sorted)
            : ListType(std::move(value_field)), keys_sorted_(keys_sorted) {
        id_ = type_id;
    }



    turbo::Result<std::shared_ptr<DataType>> MapType::create(std::shared_ptr<Field> value_field,
                                                             bool keys_sorted) {
        const auto &value_type = *value_field->type();
        if (value_field->nullable() || value_type.id() != Type::STRUCT) {
            return turbo::failed_precondition_error("Map entry field should be non-nullable struct");
        }
        const auto &struct_type = turbo::checked_cast<const StructType &>(value_type);
        if (struct_type.num_fields() != 2) {
            return turbo::failed_precondition_error("Map entry field should have two children (got ",
                                                    struct_type.num_fields(), ")");
        }
        if (struct_type.field(0)->nullable()) {
            return turbo::failed_precondition_error("Map key field should be non-nullable");
        }
        return std::make_shared<MapType>(std::move(value_field), keys_sorted);
    }

    std::string MapType::to_string(bool show_metadata) const {
        std::stringstream s;

        const auto print_field_name = [](std::ostream &os, const Field &field,
                                         const char *std_name) {
            if (field.name() != std_name) {
                os << " ('" << field.name() << "')";
            }
        };
        const auto print_field = [&](std::ostream &os, const Field &field,
                                     const char *std_name) {
            os << field.type()->to_string(show_metadata);
            print_field_name(os, field, std_name);
        };

        s << "map<";
        print_field(s, *key_field(), "key");
        s << ", ";
        print_field(s, *item_field(), "value");
        if (keys_sorted_) {
            s << ", keys_sorted";
        }
        print_field_name(s, *value_field(), "entries");
        s << ">";
        return s.str();
    }

    std::string MapType::compute_fingerprint() const {
        const auto &key_fingerprint = key_type()->fingerprint();
        const auto &item_fingerprint = item_type()->fingerprint();
        if (!key_fingerprint.empty() && !item_fingerprint.empty()) {
            std::stringstream ss;
            ss << type_id_fingerprint(*this);
            if (keys_sorted_) {
                ss << 's';
            }
            if (item_field()->nullable()) {
                ss << 'n';
            } else {
                ss << 'N';
            }
            ss << '{' << key_fingerprint + item_fingerprint << '}';
            return ss.str();
        }
        return "";
    }

}  // namespace nebula
